package aliyun

import (
	"context"
	"encoding/json"
	"errors"
	"net"

	openapi "github.com/alibabacloud-go/darabonba-openapi/client"
	slb20140515 "github.com/alibabacloud-go/slb-20140515/v3/client"
	util "github.com/alibabacloud-go/tea-utils/service"
	"github.com/sirupsen/logrus"

	"gitee.com/bjf-fhe/apicat/entry"
	"gitee.com/bjf-fhe/apicat/notify"
	"github.com/alibabacloud-go/tea/tea"
)

type Handler struct {
	client  *slb20140515.Client
	ncfg    *openapi.Config
	existed map[string]*entry.LogEntry
	dest    string
	destV6  string
}

func createClient(ncfg *openapi.Config) (_result *slb20140515.Client, _err error) {

	// 访问的域名
	ncfg.Endpoint = tea.String("slb.aliyuncs.com")
	_result = &slb20140515.Client{}
	_result, _err = slb20140515.NewClient(ncfg)
	return _result, _err
}

func (h *Handler) Init(ncfg *openapi.Config, ctx context.Context, dest, destv6 string) (err error) {

	h.client, err = createClient(ncfg)

	if err != nil {
		return
	}

	h.ncfg = ncfg
	h.dest = dest
	h.destV6 = destv6

	h.existed = make(map[string]*entry.LogEntry)

	if h.dest != "" {
		err := h.fetchAcls(h.dest)
		if err != nil {
			return err
		}
	} else if h.destV6 == "" {
		return errors.New("Aliyun Dest（阿里云配置拦截规则ID）没有设置，请使用--aliyun-dest配置该参数")
	}

	if h.destV6 != "" {
		err := h.fetchAcls(h.destV6)
		if err != nil {
			return err
		}
	}

	return err
}

func (h *Handler) fetchAcls(aclId string) (err error) {
	describeAccessControlListAttributeRequest := &slb20140515.DescribeAccessControlListAttributeRequest{
		RegionId: h.ncfg.RegionId,
		AclId:    &aclId,
	}
	runtime := &util.RuntimeOptions{}

	var result *slb20140515.DescribeAccessControlListAttributeResponse
	result, err = h.client.DescribeAccessControlListAttributeWithOptions(describeAccessControlListAttributeRequest, runtime)

	if err == nil && result.Body.AclEntrys != nil {
		for _, inServer := range result.Body.AclEntrys.AclEntry {
			var logEntry = entry.ParseString(*inServer.AclEntryComment)
			ip, _, _ := net.ParseCIDR(*inServer.AclEntryIP)
			logEntry.Client = ip.String()
			h.existed[*inServer.AclEntryIP] = logEntry
		}
	}
	return err
}

type AclEntrys []AclEntry

type AclEntry struct {
	Entry   string `json:"entry"`
	Comment string `json:"comment"`
}

func iptocidr(ip string, isIPv4 bool) string {
	var cidrSuffix string
	if isIPv4 {
		cidrSuffix = "/32"
	} else {
		cidrSuffix = "/128"
	}
	return ip + cidrSuffix
}

func (h *Handler) toEntry(item *entry.LogEntry, isIPv4 bool) (string, *AclEntry) {
	var cidr = iptocidr(item.Client, isIPv4)
	var entry = AclEntry{
		Entry: cidr,
	}
	entry.Comment = item.Description()

	return cidr, &entry

}

func (h *Handler) toEntrys(item *entry.LogEntry, isIPv4 bool) (string, string, error) {
	var entrys AclEntrys
	var cidr = iptocidr(item.Client, isIPv4)
	var entry = AclEntry{
		Entry: cidr,
	}
	entry.Comment = item.Description()
	entrys = append(entrys, entry)

	bts, err := json.Marshal(entrys)
	if err == nil {
		return cidr, string(bts), nil
	}
	return cidr, "", err
}

func (h *Handler) fromEntry(item *slb20140515.DescribeAccessControlListAttributeResponseBodyAclEntrysAclEntry) *entry.LogEntry {
	logEntry := entry.ParseString(*item.AclEntryComment)
	logEntry.Client = *item.AclEntryIP
	return logEntry
}

func (h *Handler) NotifyAdd(toAdd *entry.LogEntry) error {
	if _, existed := h.existed[toAdd.Client]; existed {
		logrus.Info(toAdd.Client, "已经存在")
		return nil
	}

	ip := net.ParseIP(toAdd.Client)

	var dest string
	var isIPv4 = ip.To4() != nil

	if !isIPv4 {
		dest = h.destV6
	} else {
		dest = h.dest
	}

	_, entrys, err := h.toEntrys(toAdd, isIPv4)
	if err != nil {
		return err
	}

	// fmt.Println(entrys)
	// entrys = `[{"entry":"10.0.1.1","comment":"privaterule1"},{"entry":"192.168.1.1","comment":"privaterule2"}]`

	_, err = h.client.AddAccessControlListEntry(&slb20140515.AddAccessControlListEntryRequest{
		RegionId:  h.ncfg.RegionId,
		AclId:     &dest,
		AclEntrys: &entrys,
	})

	if err == nil {
		goto addItemInExisted
	} else if te, ok := err.(*tea.SDKError); ok {
		if *te.Code == "InvalidParam.AclEntryExists" {
			goto addItemInExisted
		} else if *te.Code == "Throttling.User" {
			// 被限流，等待一段时间后重试
			return notify.ErrNeedRetry
		}
	}
	return err

addItemInExisted:
	toAdd.Notified = true
	h.existed[toAdd.Client] = toAdd
	return nil

}

func (h *Handler) ListExistedEntrys() (map[string]*entry.LogEntry, error) {
	return h.existed, nil
}

func (h *Handler) NotifyDel(toDel ...*entry.LogEntry) error {

	var toDels = make(map[string][]*AclEntry)

	for _, item := range toDel {
		ip := net.ParseIP(item.Client)

		var dest string
		var isIPv4 = ip.To4() != nil

		if !isIPv4 {
			dest = h.destV6
		} else {
			dest = h.dest
		}

		cidr, entry := h.toEntry(item, isIPv4)

		if _, existed := h.existed[cidr]; !existed {
			return errors.New(item.Client + "不存在")
		}

		toDels[dest] = append(toDels[dest], entry)
	}

	for k, v := range toDels {

		bts, err := json.Marshal(v)

		var entrysS = string(bts)

		if err == nil {

			var result *slb20140515.RemoveAccessControlListEntryResponse
			result, err = h.client.RemoveAccessControlListEntry(&slb20140515.RemoveAccessControlListEntryRequest{
				RegionId:  h.ncfg.RegionId,
				AclId:     &k,
				AclEntrys: &entrysS,
			})

			if err != nil {
				logrus.WithField("response", result).Error(err)
				if te, ok := err.(*tea.SDKError); ok && *te.Code == "InvalidParam.AclEntryExists" {
					for _, ip := range v {
						delete(h.existed, ip.Entry)
					}
					continue
				}
				return err
			}

		} else {
			logrus.Error(err)
			return err
		}
	}
	return nil
}
